import japa.parser.JavaParser;
import japa.parser.ParseException;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.visitor.VoidVisitorAdapter;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.Vector;

import org.joda.time.DateTime;
import org.joda.time.Days;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNLogEntryPath;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNDiffClient;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

public class proj2 {

	static SVNRepository repository;
	static ArrayList<Method> fileMethods;
	static Hashtable<String, Double> filesFix;
	static Hashtable<String, Double> methodsFix;
	static Hashtable<String, Integer> files;
	static Hashtable<String, Integer> methods;
	static Hashtable<String, Double> authors;
	static Hashtable<String, Double> authorsFix;
	public static void main( String[] args ) throws IOException, SVNException, ParseException {
		DAVRepositoryFactory.setup( );

		//Analyze each repository
		//Analyze("https://jedit.svn.sourceforge.net/svnroot/jedit", (long) 4000, (long) 4015); 
		Analyze("http://svn.svnkit.com/repos/svnkit/", (long) 4002, (long) 4008); 
		//Analyze("https://zk1.svn.sourceforge.net/svnroot/zk1/", (long) 9000, (long) 9100);		
		
		/*
		//Report 4
		// jedit-4-0-pre4 to jedit-4-0-pre8/
		findBugsVsTime("https://jedit.svn.sourceforge.net/svnroot/jedit", (long) 3993, (long)  4013, (long) 4052, (long) 4084, (long) 4114);
		System.out.println("New Repository");
		//1.1.1 to 1.1.5
		findBugsVsTime("http://svn.svnkit.com/repos/svnkit/", (long) 2930, (long)  3019, (long) 3176, (long) 3246, (long) 3817);
		System.out.println("New Repository");
		// 3.0.1 to 3.0.5
		findBugsVsTime("https://zk1.svn.sourceforge.net/svnroot/zk1/", (long) 5839, (long)  6289, (long) 6500, (long) 7400, (long) 8072);
		 */
	}

	public static boolean Analyze(String url, long startRevision, long endRevision) throws SVNException, FileNotFoundException, ParseException {
		filesFix = new Hashtable<String, Double>();
		methodsFix = new Hashtable<String, Double>();
		files = new Hashtable<String, Integer>();
		methods = new Hashtable<String, Integer>();
		authors = new Hashtable<String, Double>();
		authorsFix = new Hashtable<String, Double>();
		//Create a way to access each repository
		repository = SVNRepositoryFactory.create( SVNURL.parseURIEncoded( url ) );

		//Create authentication manager
		String name = "anonymous";
		String password = "anonymous";
		ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager( name, password );
		Collection logEntries = null; //collection of all the log entries
		logEntries = repository.log( new String[] { "" } , null , startRevision , endRevision , true , true );

		SVNURL svnurl = repository.getLocation();

		int totalLinesModified = 0;
		int totalFilesModified = 0;
		int totalLinesModifiedFix = 0;
		int totalFilesModifiedFix = 0;
		int totalMethodsModified = 0;
		int totalMethodsModifiedFix = 0;

		SVNLogEntry logEntry = null;
		SVNLogEntry prevEntry;

		for (Iterator entries = logEntries.iterator(); entries.hasNext();) {
			prevEntry = logEntry;
			logEntry = (SVNLogEntry) entries.next();
			long revision = logEntry.getRevision(); //revision long
			String author;
			if(logEntry.getAuthor() == null){
				author = "NULL";
			}
			else{
				author = logEntry.getAuthor();
			}

			Date date = logEntry.getDate();
			String message = logEntry.getMessage();
			boolean isFix = false;
			if(logEntry.getMessage().toLowerCase().contains("fix") || logEntry.getMessage().contains("bug") || logEntry.getMessage().contains("error")|| logEntry.getMessage().contains("defect") || logEntry.getMessage().contains("patch")){				isFix = true;
			if (authorsFix.containsKey(author)){
				double numprev = authorsFix.get(author);
				numprev++;
				authorsFix.put(author, numprev);
			}
			else{
				authorsFix.put(author, 1.0);
			}
			}
			if (authors.containsKey(author)){
				double numprev = authors.get(author);
				numprev++;
				authors.put(author, numprev);
			}
			else{
				authors.put(author, 1.0);
			}

			System.out.println("---------------------------------------------");
			System.out.println("revision: " + revision);
			System.out.println("author: " + author);
			System.out.println("date: " + date);
			System.out.println("log message: " + message);

			if (logEntry.getChangedPaths().size() > 0) {
				System.out.println();
				System.out.println("changed paths:");
				Set changedPathsSet = logEntry.getChangedPaths().keySet();

				int nFilesAdded = 0;
				int nFilesModified = 0;
				int nFilesDeleted = 0;


				//For each changed file/folder
				for (Iterator changedPaths = changedPathsSet.iterator(); changedPaths.hasNext();) {

					SVNLogEntryPath entryPath = (SVNLogEntryPath) logEntry.getChangedPaths().get(changedPaths.next());
					SVNNodeKind nodeKind = repository.checkPath(entryPath.getPath(),logEntry.getRevision());

					//If it is a file/missing node
					if ((nodeKind == SVNNodeKind.FILE || nodeKind == SVNNodeKind.NONE) && entryPath.toString().contains(".java") && (entryPath.getType() == 'A' || entryPath.getType() == 'D'||entryPath.getType() == 'M'      )    ) {


						if (files.containsKey(entryPath.getPath())){
							int numprev = files.get(entryPath.getPath());
							numprev++;
							files.put(entryPath.getPath(), numprev);
						}
						else{
							files.put(entryPath.getPath(), 1);
						}



						if(isFix == true){
							if (filesFix.containsKey(entryPath.getPath())){
								double numprev = filesFix.get(entryPath.getPath());
								numprev++;
								filesFix.put(entryPath.getPath(), numprev);
							}
							else{
								filesFix.put(entryPath.getPath(), 1.0);
							}

						}



						if (entryPath.getType() == 'A') { // File added
							nFilesAdded++;
							numLinesAndMethods numLandM = returnNumNewLinesAndMethodsInFile(repository,entryPath, logEntry);
							int newlinesinfile = numLandM.numLines;
							int methodsinfile = numLandM.numMethods;
							totalMethodsModified += methodsinfile;
							totalLinesModified += newlinesinfile;
							totalFilesModified++;
							if(isFix == true){
								totalMethodsModifiedFix += methodsinfile;
								totalLinesModifiedFix += newlinesinfile;
								totalFilesModifiedFix++;
							}

						} else if (entryPath.getType() == 'M') { // Modified
							SVNRevision mySVNRevision = SVNRevision.create(revision);
							SVNRevision mySVNRevisionPrev = SVNRevision.create(revision-1);
							numLinesAndMethods numLandM =  getChangedLinesAndMethods(url, mySVNRevision, entryPath.getPath(), mySVNRevisionPrev,entryPath.getPath(), isFix);
							int numlines = numLandM.numLines;
							int numMethods = numLandM.numMethods;
							totalMethodsModified += numMethods;
							totalLinesModified += numlines;
							nFilesModified++;
							totalFilesModified++;
							if(isFix == true){
								totalLinesModifiedFix += numlines;
								totalFilesModifiedFix++;
								totalMethodsModifiedFix += numMethods;
							}

						} else if (entryPath.getType() == 'D') { // Deleted
							nFilesDeleted++;

							numLinesAndMethods numLandM = returnNumNewLinesAndMethodsInFile(repository,entryPath, prevEntry);
							int numlines = numLandM.numLines;
							int nummethods = numLandM.numMethods;
							totalMethodsModified += nummethods;
							totalLinesModified += numlines;
							totalFilesModified++;
							if(isFix == true){
								totalMethodsModified += nummethods;
								totalLinesModifiedFix += numlines;
								totalFilesModifiedFix++;
							}
						}
					}
				}
				System.out.println("Files Added: " + nFilesAdded);
				System.out.println("Files Modified: " + nFilesModified);
				System.out.println("Files Deleted: " + nFilesDeleted);
				System.out.println();
			}

		}
		System.out.println(totalFilesModified + " Total Files Changed");
		System.out.println(totalFilesModified/5 + " Files Changed Per Revision");
		System.out.println(totalLinesModified + " Total Lines Changed");
		System.out.println(totalLinesModified/5 + " Lines Changed Per Revision");
		System.out.println(totalMethodsModified + " Total Methods Changed");
		System.out.println(totalMethodsModified/5 + " Methods Changed Per Revision");



		System.out.println();
		System.out.println(totalFilesModifiedFix + " Total Files Changed in 'Fixed' Revisions");
		System.out.println(totalFilesModifiedFix/5 + " Files Changed Per Revision");
		System.out.println(totalLinesModifiedFix + " Total Lines Changed in 'Fixed' Revisions");
		System.out.println(totalLinesModifiedFix/5 + " Lines Changed Per Revision");
		System.out.println(totalMethodsModifiedFix + " Total Methods Changed in 'Fixed' Revisions");
		System.out.println(totalMethodsModifiedFix/5 + " Methods Changed Per Revision");
		System.out.println();

		ArrayList filesFixArrayList=new ArrayList(filesFix.entrySet());
		ArrayList methodsFixArrayList=new ArrayList(methodsFix.entrySet());
		ArrayList authorsFixArrayList=new ArrayList(authorsFix.entrySet());

		System.out.println("Most Frequently 'Fixed' Files");
		printSortedArrayList(filesFixArrayList);

		System.out.println();

		System.out.println("Most Frequently 'Fixed' Methods");
		printSortedArrayList(methodsFixArrayList);

		Hashtable<String, Double> avgFilesFix = new Hashtable<String, Double>();
		Hashtable<String, Double> avgMethodsFix = new Hashtable<String, Double>();
		Hashtable<String, Double> avgauthorsFix = new Hashtable<String, Double>();
		Enumeration keys = filesFix.keys();
		Enumeration keys2 = methodsFix.keys();
		Enumeration keys3 = authors.keys();

		while( keys.hasMoreElements() ) {
			String key =(String) keys.nextElement();
			double value = filesFix.get(key);
			double totalChanges = files.get(key);
			avgFilesFix.put((String)key, (double)value/totalChanges);
		}
		while( keys2.hasMoreElements() ) {
			String key =(String) keys2.nextElement();
			double value =(double) methodsFix.get(key);
			double totalChanges = (double)methods.get(key);
			avgMethodsFix.put((String)key, (double)value/totalChanges);
		}
		while( keys3.hasMoreElements() ) {
			String key = (String) keys3.nextElement();
			double totalChanges =(double) authors.get(key);
			if(authorsFix.containsKey(key)){
				double value = (double)authorsFix.get(key);
				avgauthorsFix.put((String)key, (double) value/totalChanges);
			}
			else{

				avgauthorsFix.put((String)key, 0.0);
			}

		}

		//this should print the files, and the ratios in sorted order. 
		ArrayList avgFilesFixToSort =new ArrayList(avgFilesFix.entrySet());
		System.out.println("Highest Ratios for Files");
		printSortedArrayList(avgFilesFixToSort);

		System.out.println();

		ArrayList avgMethodsFixToSort =new ArrayList(avgMethodsFix.entrySet());
		System.out.println("Highest Ratios for Methods");
		printSortedArrayList(avgMethodsFixToSort);

		System.out.println();

		ArrayList avgauthorsFixToSort = new ArrayList(avgauthorsFix.entrySet());
		System.out.println("Highest fixes for authors");
		printSortedArrayList(authorsFixArrayList);
		System.out.println("Highest ratios for authors");
		printSortedArrayList(avgauthorsFixToSort);
		return true;
	}

	static void printSortedArrayList(ArrayList myArrayList){
		Collections.sort(myArrayList, new MyComparator());
		Iterator itr=myArrayList.iterator();
		String key="";
		double value=0;
		int cnt=0;
		//Hashtable<String, Integer> toReturn = new Hashtable<String, Integer>();
		while(itr.hasNext() && cnt < 20) {
			cnt++;
			Map.Entry e=(Map.Entry)itr.next();
			key = (String)e.getKey();
			value = ((Double)e.getValue()).doubleValue();
			//toReturn.put(key, value);
			System.out.printf(key+"\t");
		}
		Iterator it = myArrayList.iterator();
		System.out.println();
		cnt = 0;
		while(it.hasNext() && cnt < 20) {
			cnt++;
			Map.Entry e=(Map.Entry)it.next();
			value = ((Double)e.getValue()).doubleValue();
			//toReturn.put(key, value);
			System.out.printf(value+"\t");
		}
		System.out.println();
		
		//	return toReturn;
	}

	static class MyComparator implements Comparator{

		public int compare(Object obj1, Object obj2){

			int result=0;Map.Entry e1 = (Map.Entry)obj1 ;
			Map.Entry e2 = (Map.Entry)obj2 ;//Sort based on values.
			Double value1 = (Double)e1.getValue();
			Double value2 = (Double)e2.getValue();
			if(value1.compareTo(value2)==0){
				String word1=(String)e1.getKey();
				String word2=(String)e2.getKey();
				//Sort String in an alphabetical order
				result=word1.compareToIgnoreCase(word2);
			} else{
				//Sort values in a descending order
				result=value2.compareTo( value1 );
			}
			return result;
		}
	}
	@SuppressWarnings("deprecation")
	private static numLinesAndMethods getChangedLinesAndMethods(String url, SVNRevision rN, String filePathN, SVNRevision rM, String filePathM, boolean isFix) throws SVNException {
		SVNClientManager clientManager = SVNClientManager.newInstance();
		SVNDiffClient diffClient = clientManager.getDiffClient();
		ByteArrayOutputStream baos = new ByteArrayOutputStream();	
		ByteArrayOutputStream FileContentsbaos = new ByteArrayOutputStream();
		SVNProperties fileProperties = new SVNProperties();
		repository.getFile(filePathN, rN.getNumber(), fileProperties, FileContentsbaos);
		fileMethods = new ArrayList<Method>();
		int changedMethods = 0;
		boolean parseWorked = false;


		try {
			CompilationUnit c = JavaParser.parse(new ByteArrayInputStream(FileContentsbaos.toByteArray()));
			new MethodVisitor().visit(c, null);
			parseWorked = true;
		} catch (Exception e) {
			System.out.println("Can't parse this file: Invalid file");
			parseWorked = false;
		}
		try {
		
			
			diffClient.doDiff(SVNURL.parseURIEncoded(url+ filePathN), rN, SVNURL.parseURIEncoded(url+ filePathN), rM, true, true, baos);
			int numChangedLines = 0;
			String st = baos.toString();
			Scanner s = new Scanner(st);
			for(int i = 0; i < 4; i++)
				s.nextLine();
			while(s.hasNextLine()) {
				String line = s.nextLine();
				if(line.charAt(0) == '-' || line.charAt(0) == '+')
					numChangedLines++;
				if(parseWorked && line.charAt(0) == '@') {
					int startModLineNumber = 0;
					int endModLineNumber = 0;
					for(int i=0; i< line.length(); i++) {
						if( line.charAt(i) == '+')
							startModLineNumber = i;
						if( line.charAt(i) == ',')
							endModLineNumber = i;
					}
					String ModLineNumber = line.substring(startModLineNumber+1, endModLineNumber);
					int lineNumber = Integer.parseInt(ModLineNumber);

					for(int i = 0; i < fileMethods.size(); i++) {
						if(lineNumber >= fileMethods.get(i).startLine && lineNumber <= fileMethods.get(i).endLine)  {
							changedMethods++;
							String methodName = fileMethods.get(i).id;

							if (methods.containsKey(methodName)){
								int numprev = methods.get(methodName);
								numprev++;
								methods.put(methodName, numprev);
							}
							else{
								methods.put(methodName, 1);
							}


							if(isFix == true){
								if (methodsFix.containsKey(methodName)){
									double numprev = methodsFix.get(methodName);
									numprev++;
									methodsFix.put(methodName, numprev);
								}
								else{
									methodsFix.put(methodName, 1.0);
								}

							}
							fileMethods.remove(i);
							i = fileMethods.size() + 2;
						}
					}

				}

			}
			return new numLinesAndMethods(numChangedLines, changedMethods);
		}catch (SVNException e){
		}




		
		//System.out.println("Changed Methods:" + changedMethods);
		return new numLinesAndMethods(0,0);
	}

	private static class MethodVisitor extends VoidVisitorAdapter {
		public void visit(MethodDeclaration n, Object arg) {   
			Method newMethod = new Method();
			newMethod.id = n.getName();
			newMethod.startLine = n.getBeginLine();
			newMethod.endLine = n.getEndLine();	
			fileMethods.add(newMethod); 
		}
	}

	public static numLinesAndMethods returnNumNewLinesAndMethodsInFile(SVNRepository repository, SVNLogEntryPath entryPath, SVNLogEntry logEntry) throws SVNException {
		int nNewlines = 0;
		SVNProperties fileProperties = new SVNProperties();
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		repository.getFile(entryPath.getPath(), logEntry.getRevision(), fileProperties, baos);
		fileMethods = new ArrayList<Method>();

		try {
			CompilationUnit c = JavaParser.parse(new ByteArrayInputStream(baos.toByteArray()));
			new MethodVisitor().visit(c, null);
		} catch (Exception e) {
			System.out.println("Can't parse this file: Invalid file");
		}

		String st = baos.toString();
		Scanner s = new Scanner(st);
		while(s.hasNextLine()) {
			s.nextLine();
			nNewlines++;
		}
		return new numLinesAndMethods(nNewlines, fileMethods.size());
	}

	public static void findBugsVsTime(String url, long r1, long r2, long r3, long r4, long r5) throws SVNException {
		SVNRepository repository = SVNRepositoryFactory.create( SVNURL.parseURIEncoded( url ) );
		String name = "anonymous";
		String password = "anonymous";
		ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager( name, password );
		Collection logEntries = null; //collection of all the log entries
		logEntries = repository.log( new String[] { "" } , null ,  r1, r5, true , true );
		ArrayList<Integer> numFixes = new ArrayList();
		ArrayList<Integer> days = new ArrayList();	
		Iterator entries = logEntries.iterator(); 
		SVNLogEntry firstEntry = (SVNLogEntry) entries.next();
		Date prevDate = firstEntry.getDate();
		Date firstDate = prevDate;
		while (entries.hasNext()) {
			SVNLogEntry logEntry = (SVNLogEntry) entries.next();
			long revisionNumber = logEntry.getRevision();
			Date date = logEntry.getDate();
			
			if(logEntry.getMessage().toLowerCase().contains("fix") || logEntry.getMessage().contains("bug") || logEntry.getMessage().contains("error")|| logEntry.getMessage().contains("defect") || logEntry.getMessage().contains("patch")){
				if(Days.daysBetween(new DateTime(prevDate), new DateTime(date)).getDays() == 0 && days.size() > 0) 
					numFixes.set(numFixes.size()-1, numFixes.get(numFixes.size()-1) + 1);
				else {
					int dayDiff = Days.daysBetween(new DateTime(firstDate), new DateTime(date)).getDays();
					if( dayDiff < 0 )
					{
						System.out.println("First:" + firstDate);
						System.out.println("last:" + date);
					}
					days.add( dayDiff  );
					numFixes.add(1);
				}
			}
			if(revisionNumber == r2 || revisionNumber == r3 || revisionNumber == r4 || revisionNumber == r5) {
				System.out.println("----------------------------------");
				System.out.println("Revision number: " + revisionNumber);
				for(int i = 0; i < days.size(); i++) {
					System.out.printf(days.get(i) + "\t" + numFixes.get(i) + "\n");
				}
				days = new ArrayList<Integer>();
				numFixes = new ArrayList<Integer>();
				firstDate = date;
			}
			prevDate = date;
		}
	}
}

class Method {
	public String id;
	public int startLine;
	public int endLine;
	public Method() {
		/*id = idIn;
		startLine = startLineIn;
		endLine = endLineIn;*/
	}
}

class numLinesAndMethods {
	public numLinesAndMethods(int numChangedLines, int changedMethods) {
		numLines = numChangedLines;
		numMethods = changedMethods;
	}
	public int numLines;
	public int numMethods;
}
